﻿using System;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Collections.Generic;
using System.Diagnostics;

namespace Rabbit.OAuthCore
{
    /// <summary>
    /// OAuthHelper, you can get the Access Token and Access Secret
    /// you also can send an signed Request with this helper tool
    /// </summary>
    public class OAuthHelper
    {
        OAuthBase oAuthBase = new OAuthBase();
        IWebProxy Proxy = WebRequest.GetSystemWebProxy();
        private static object obj = new object();
        #region Base Url
        public string RequestTokenUrl { get; set; }
        public string AuthorizationTokenUrl { get; set; }
        public string AccessTokenUrl { get; set; }
        #endregion

        #region OAuth Parameters
        /// <summary>
        /// the consumer key, you got this from the the service provider, such as Sina/Sohu/Twitter, etc.
        /// </summary>
        public string ConsumerKey { get; set; }
        /// <summary>
        /// consumer secret, you got this from the the service provider, such as Sina/Sohu/Twitter, etc.
        /// </summary>
        public string ConsumerSecret { get; set; }

        /// <summary>
        /// access token, value of this will be generated at 3rd step of the workflow in OAuth authorization
        /// </summary>
        public string AccessToken { get; set; }
        /// <summary>
        /// access token Secret, value of this will be generated at 3rd step of the workflow in OAuth authorization
        /// </summary>
        public string AccessTokenSecret { get; set; }

        /// <summary>
        /// VerifyCode, value of this will be generated at 2nd step of the workflow in OAuth authorization,
        /// which is provided by the user input or by the service provider
        /// </summary>
        public string VerifyCode { get; set; }

        private string RequestToken = "";
        private string RequestTokenSecret = "";
        #endregion

        /// <summary>
        /// init
        /// </summary>
        /// <param name="requestTokenUrl"></param>
        /// <param name="authorizationTokenUrl"></param>
        /// <param name="accessTokenUrl"></param>
        public OAuthHelper(string requestTokenUrl, string authorizationTokenUrl, string accessTokenUrl)
        {
            this.RequestTokenUrl = requestTokenUrl;
            this.AuthorizationTokenUrl = authorizationTokenUrl;
            this.AccessTokenUrl = accessTokenUrl;
        }

        public OAuthHelper(string requestTokenUrl, string authorizationTokenUrl, string accessTokenUrl, string consumerKey, string consumerSecret)
        {
            this.RequestTokenUrl = requestTokenUrl;
            this.AuthorizationTokenUrl = authorizationTokenUrl;
            this.AccessTokenUrl = accessTokenUrl;
            this.ConsumerKey = consumerKey;
            this.ConsumerSecret = consumerSecret;
        }

        /// <summary>
        /// get the authorization url, use should go to this page to get the verify code and then put it to the property of VerifyCode
        /// </summary>
        /// <returns></returns>
        public string GetAuthorizationUrl(HttpMethod method = HttpMethod.GET)
        {
            string outUrl;
            string outParameters;

            string signature;
            signature = oAuthBase.GenerateRequestTokenSignature(
                new Uri(this.RequestTokenUrl),
                ConsumerKey,
                ConsumerSecret,
                method,
                oAuthBase.GenerateTimeStamp(),
                oAuthBase.GenerateNonce(),
                out outUrl,
                out outParameters);

            outParameters += "&oauth_signature=" + HttpUtility.UrlEncode(signature);
            string responseData = OAuthWebRequest(outUrl, outParameters, method);

            NameValueCollection requestToken = HttpUtility.ParseQueryString(responseData);
            if (requestToken[OAuthBase.OAuthTokenKey] == null || requestToken[OAuthBase.OAuthTokenSecretKey] == null)
            {
                throw new Exception("can not get authorize url, please check your consumerKey and consumerSecret");
            }
            else
            {
                //save this token and secret, it will be used in the 3rd step
                RequestToken = requestToken[OAuthBase.OAuthTokenKey];
                RequestTokenSecret = requestToken[OAuthBase.OAuthTokenSecretKey];
            }

            return AuthorizationTokenUrl + "?" + responseData;
        }



        public void GenerateAccessToken(string verifyCode, HttpMethod method = HttpMethod.GET)
        {
            //add the verifycode to the parameters that to be signed
            oAuthBase.AdditionalParameters.Clear();
            oAuthBase.AdditionalParameters.Add(new OAuthBase.QueryParameter(OAuthBase.OAuthVerifyCodeKey, verifyCode));

            string normalizedUrl, normalizedRequestParameters;
            string signature = oAuthBase.GenerateAccessTokenSignature(
                new Uri(AccessTokenUrl),
                ConsumerKey,
                ConsumerSecret,
                RequestToken,
                RequestTokenSecret,
                method,
                oAuthBase.GenerateTimeStamp(),
                oAuthBase.GenerateNonce(),
                out normalizedUrl,
                out normalizedRequestParameters);

            normalizedRequestParameters += "&oauth_signature=" + HttpUtility.UrlEncode(signature);
            string responseData = OAuthWebRequest(normalizedUrl, normalizedRequestParameters, method);

            NameValueCollection requestToken = HttpUtility.ParseQueryString(responseData);
            if (requestToken[OAuthBase.OAuthTokenKey] == null || requestToken[OAuthBase.OAuthTokenSecretKey] == null)
            {
                throw new Exception("can not get authorize url, please check your consumerKey and consumerSecret");
            }
            else
            {
                //save this token and secret, it will be used in the 3rd step
                AccessToken = requestToken[OAuthBase.OAuthTokenKey];
                AccessTokenSecret = requestToken[OAuthBase.OAuthTokenSecretKey];
            }
        }

        /// <summary>
        /// you can call this method directly to request protected resources after you get the access token
        /// </summary>
        /// <param name="Url">url</param>
        /// <param name="otherData">
        /// form data, like this:
        /// status=statusValue&postID=postID&...
        /// </param>
        /// <param name="method">GET,POST</param>
        /// <returns>response data that returned from server</returns>
        public string SendRequest(string url, string otherData, HttpMethod method = HttpMethod.GET)
        {
            if (string.IsNullOrEmpty(AccessToken) || string.IsNullOrEmpty(AccessTokenSecret))
            {
                throw new InvalidOperationException(" Invalid accesstoken ,please call the method GenerateAccessToken before you call this method to get the access token");
            }

            //NameValueCollection postDataCollection = HttpUtility.ParseQueryString(otherData);

            //we do not need additional parameters( like verify code) anymore, so clear them
            oAuthBase.AdditionalParameters.Clear();
            if (!string.IsNullOrEmpty(otherData))
            {
                string[] parameters = otherData.Split('&');
                foreach (var parameter in parameters)
                {
                    var keyvaluepair = parameter.Split('=');
                    oAuthBase.AdditionalParameters.Add(new OAuthBase.QueryParameter(keyvaluepair[0], keyvaluepair[1]));
                }
            }
            string normalizedUrl, normalizedRequestParameters;
            string signature = oAuthBase.GenerateAuthorizationHeaderString(
                new Uri(url),
                this.ConsumerKey,
                this.ConsumerSecret,
                this.AccessToken,
                this.AccessTokenSecret,
                method,
                oAuthBase.GenerateTimeStamp(),
                oAuthBase.GenerateNonce(),
                out normalizedUrl,
                out normalizedRequestParameters);

            if (method == HttpMethod.GET)
            {
                url = RebuildUrl(url, otherData);
            }

            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
            if (Proxy != null)
            {
                request.Proxy = Proxy;
            }

            request.Accept = "*/*";
            request.Headers.Add("Authorization:" + signature);
            request.Method = method.ToString();

            if (method == HttpMethod.POST)
            {
                request.ContentType = "application/x-www-form-urlencoded";
                StreamWriter requestWriter = new StreamWriter(request.GetRequestStream());
                requestWriter.Write(otherData);
                requestWriter.Close();
            }

            try
            {
                StreamReader responseReader = new StreamReader(request.GetResponse().GetResponseStream());
                return responseReader.ReadToEnd();
            }
            catch(Exception ex)
            {
                Debug.Print(ex.Message);
                Debug.Print(ex.StackTrace);
                throw; 
            }
        }

        /// <summary>
        /// you can call this method directly to request protected resources after you get the access token
        /// </summary>
        /// <param name="Url">url</param>
        /// <param name="pictureRemark">Picture remark</param>
        /// <param name="picturePath">picture path to be updloaded</param>
        /// <param name="method">GET,POST</param>
        /// <returns>response data that returned from server</returns>
        public string SendPostWithPicture(string url, string pictureRemark, string picturePath)
        {
            return SendPostWithPicture(url, pictureRemark, File.OpenRead(picturePath)); 
        }

        /// <summary>
        /// you can call this method directly to request protected resources after you get the access token
        /// </summary>
        /// <param name="Url">url</param>
        /// <param name="pictureRemark">Picture remark</param>
        /// <param name="picturePath">picture path to be updloaded</param>
        /// <param name="method">GET,POST</param>
        /// <returns>response data that returned from server</returns>
        public string SendPostWithPicture(string url, string pictureRemark, Stream pictureStream)
        {
            if (string.IsNullOrEmpty(AccessToken) || string.IsNullOrEmpty(AccessTokenSecret))
            {
                pictureStream.Close();
                throw new InvalidOperationException(" invalid accesstoken ,please call the method GenerateAccessToken before you call this method to get the access token");
            }

            //pictureRemark = this.UrlEncode(pictureRemark);
            //we do not need additional parameters( like verify code) anymore, so clear them
            oAuthBase.AdditionalParameters.Clear();
            oAuthBase.AdditionalParameters.Add(new OAuthBase.QueryParameter("status", pictureRemark));

            string normalizedUrl, normalizedRequestParameters;
            string signature = oAuthBase.GenerateAuthorizationHeaderString(
                new Uri(url),
                this.ConsumerKey,
                this.ConsumerSecret,
                this.AccessToken,
                this.AccessTokenSecret,
                HttpMethod.POST,
                oAuthBase.GenerateTimeStamp(),
                oAuthBase.GenerateNonce(),
                out normalizedUrl,
                out normalizedRequestParameters);

            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);

            if (Proxy != null)
            {
                request.Proxy = Proxy;
            }
            string boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x");

            request.Method = HttpMethod.POST.ToString();
            request.Headers.Add("Authorization:" + signature);
            request.ContentType = "multipart/form-data; boundary=" + boundary;

            string content = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"status\"\r\n\r\n";
            string pic = "\r\n--" + boundary + "\r\nContent-Disposition: form-data; name=\"pic\"; filename=\"postpic.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
            string end_data = "\r\n--" + boundary + "--\r\n";
            byte[] fileData = new byte[pictureStream.Length];
            pictureStream.Seek(0, SeekOrigin.Begin);
            pictureStream.Read(fileData, 0, (int)pictureStream.Length);

            Stream writer = request.GetRequestStream();
            WriteData(writer, Encoding.UTF8.GetBytes(content));
            WriteData(writer, Encoding.UTF8.GetBytes(pictureRemark));
            WriteData(writer, Encoding.UTF8.GetBytes(pic));
            WriteData(writer, fileData);

            WriteData(writer, Encoding.UTF8.GetBytes(end_data));

            writer.Close();
            try
            {
                StreamReader responseReader = new StreamReader(request.GetResponse().GetResponseStream());
                return responseReader.ReadToEnd();
            }
            catch
            {
                throw;
            }
            finally
            {
                fileData = null;
                pictureStream.Close();
            }
        }

        private void WriteData(Stream writer, byte[] data)
        {
            writer.Write(data, 0, data.Length);
        }

        private string RebuildUrl(string url, string queryString)
        {
            if (url.Substring(url.Length - 1, 1) == "?")
            {
                url += "&" + queryString;
            }
            else if (!string.IsNullOrEmpty(queryString))
            {
                url += "?" + queryString;
            }
            return url;
        }

        private string OAuthWebRequest(string url, string otherData, HttpMethod method = HttpMethod.GET)
        {
            if (otherData != "" && method == HttpMethod.GET)
            {
                url = RebuildUrl(url, otherData);
            }

            HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(url);

            if (Proxy != null)
            {
                webRequest.Proxy = Proxy;
            }
            if (otherData != "" && method == HttpMethod.POST)
            {
                byte[] dataStream = System.Text.Encoding.UTF8.GetBytes(otherData);

                webRequest.ContentType = "application/x-www-form-urlencoded";
                webRequest.ContentLength = dataStream.Length;
                Stream streamWriter = webRequest.GetRequestStream();
                streamWriter.Write(dataStream, 0, dataStream.Length);
            }

            try
            {
                StreamReader responseStream = new StreamReader(webRequest.GetResponse().GetResponseStream());
                return responseStream.ReadToEnd();
            }
            catch
            {
                throw;
            }
        }
    }
}
